package com.sunday.common.netty.e2_Time;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

/**
 * 本节中要实现的协议是 TIME 协议。
 * 它与前面的示例不同，因为它发送一条包含 32 位整数的消息，而不接收任何请求，并在发送消息后关闭连接。
 * 在此示例中，您将学习如何构造和发送消息，以及如何在完成后关闭连接。
 * 因为我们将忽略任何接收到的数据，而是在建立连接后立即发送消息，所以这次我们不能使用该方法。
 * 相反，我们应该重写该方法。实现如下：channelRead() channelActive()
 */
@Slf4j
public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 如前所述，当建立连接并准备好生成流量时，将调用该方法。
     * 让我们编写一个 32 位整数，表示此方法中的当前时间。channelActive()
     */
    @Override
    public void channelActive(final ChannelHandlerContext ctx) { // (1)
        /**
         * 要发送新消息，我们需要分配一个新的缓冲区来包含该消息。
         * 我们将写入一个 32 位整数，因此我们需要一个容量至少为 4 字节的 ByteBuf。
         * 获取当前 ByteBufAllocator via 并分配新的缓冲区。ChannelHandlerContext.alloc()
         * ctx.alloc() 获取当前 ChannelHandlerContext 的 ByteBufAllocator。
         * ctx.alloc().buffer(4) 创建一个新的缓冲区，其容量为 4 字节。
         */
        final ByteBuf time = ctx.alloc().buffer(4); // (2)
        // time.writeInt 方法将一个整数写入到缓冲区中。
        int timeMillis = (int) (System.currentTimeMillis() / 1000L + 2208988800L);
        log.info("channelActive : {}", timeMillis);
        time.writeInt(timeMillis);
        /**
         * 像往常一样，我们编写构造的消息。
         * 但是等等，翻转在哪里？我们以前不是在蔚来汽车发消息之前打电话吗？
         * 没有这样的方法，因为它有两个指针;一个用于读取操作，另一个用于写入操作。
         * 当你写一些东西时，编写器索引会增加，而读取器索引不会改变。
         * 读取器索引和编写器索引分别表示消息的开始和结束位置。java.nio.ByteBuffer.flip() ByteBufByteBuf
         *
         * 相比之下，NIO 缓冲区不提供一种干净的方法来确定消息内容的开始和结束位置，而无需调用 flip 方法。
         * 当您忘记翻转缓冲区时，您会遇到麻烦，因为不会发送任何内容或不正确的数据。
         * 这样的错误在Netty中不会发生，因为我们对不同的操作类型有不同的指针。
         * 当你习惯了它时，你会发现它让你的生活更轻松——一种没有翻转的生活！
         *
         * 需要注意的另一点是 （and ） 方法返回一个 ChannelFuture。
         * ChannelFuture 表示尚未发生的 I/O 操作。
         * 这意味着，任何请求的操作可能尚未执行，因为所有操作在 Netty 中都是异步的。
         * 例如，以下代码甚至可能在发送消息之前关闭连接：ChannelHandlerContext.write() writeAndFlush()
         *
         * Channel ch = ...;
         * ch.writeAndFlush(message);
         * ch.close();
         *
         * 因此，您需要在 ChannelFuture 完成后调用该方法，该方法由该方法返回，并在完成写入操作时通知其侦听器。
         * 请注意，也可能不会立即关闭连接，它会返回 ChannelFuture。close()write()close()
         */
        final ChannelFuture f = ctx.writeAndFlush(time); // (3)

        /**
         * 那么，当写入请求完成时，我们如何收到通知？这与将 ChannelFutureListener 添加到返回的 .
         * 在这里，我们创建了一个新的匿名 ChannelFutureListener，它在操作完成时关闭。ChannelFutureChannel
         * 或者，您可以使用预定义的侦听器来简化代码：
         * f.addListener(ChannelFutureListener.CLOSE);
         */
        f.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) {
                assert f == future;
                ctx.close();
            }
        }); // (4)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

}
